package main.com.bigcompany.doublelinkedlist;

import java.util.Iterator;
import java.util.NoSuchElementException;

public class DoublyLinkedList<E> implements Iterable<E>{

    private Node first;

    private class Node {
        final E object;
        Node prev;
        Node next;

        private Node(E object) {
            this.object = object;
        }

        private Node(E object, Node prev) {
            this.object = object;
            this.prev = prev;
        }

        @Override
        public String toString() {
            return object.toString();
        }
    }

    @Override
    public Iterator<E> iterator() {
        return new Iterator<E>() {
            private Node currentNode = first;

            @Override
            public boolean hasNext() {
                return currentNode != null;
            }

            @Override
            public E next() {
                E object = currentNode.object;
                currentNode = currentNode.next;
                return object;
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }

    public Iterator<E> reverseIterator() {
        final Node lastNode = getLastNode();

        return new Iterator<E>() {
            private Node currentNode = lastNode;

            @Override
            public boolean hasNext() {
                return currentNode != null;
            }

            @Override
            public E next() {
                E object = currentNode.object;
                currentNode = currentNode.prev;
                return object;
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }

    private Node getLastNode() {
        Node lastNode = null;
        Node currentNode = first;

        while (currentNode != null) {
            if (currentNode.next == null) lastNode = currentNode;
            currentNode = currentNode.next;
        }
        return lastNode;
    }

    private Node getFirstNodeContainsObject(E object) {
        if (first == null) return null;
        Node iterationNode = first;
        while (iterationNode != null) {
            if (object != null && object.equals(iterationNode.object)) return iterationNode;
            if (object == null && iterationNode.object == null) return iterationNode;
            iterationNode = iterationNode.next;
        }
        return null;
    }

    private void testHasElement() {
        if (first == null) throw new NoSuchElementException();
    }

    public boolean isEmpty() {
        return first == null;
    }

    public void add(E object) {
        addLast(object);
    }

    public void addFirst(E object) {
        if (first == null) {
            first = new Node(object);
        } else {
            Node newNode = new Node(object);
            first.prev = newNode;
            newNode.next = first;
            first = newNode;
        }
    }

    public void addLast(E object) {
        if (first == null) {
            first = new Node(object);
        } else {
            Node lastNode = getLastNode();
            lastNode.next = new Node(object, lastNode);
        }
    }

    public boolean remove(E object) {
        testHasElement();
        Node removeNode = getFirstNodeContainsObject(object);

        if (removeNode == null) return false;

        if (removeNode.prev == null) {
            removeFirst();
            return true;
        } if (removeNode.next == null) {
            removeLast();
            return true;
        }

        Node prevNode = removeNode.prev;
        Node nextNode = removeNode.next;
        prevNode.next = nextNode;
        nextNode.prev = prevNode;

        return true;
    }

    public E removeFirst() {
        testHasElement();

        E object = first.object;

        if (first.next == null) {
            first = null;
        } else {
            first = first.next;
            first.prev = null;
        }
        return object;
    }

    public E removeLast() {
        testHasElement();

        Node lastNode = getLastNode();

        E object = lastNode.object;

        Node newLastNode = lastNode.prev;
        newLastNode.next = null;

        return object;
    }

    public E getFirst() {
        testHasElement();
        return first.object;
    }

    public E getLast() {
        testHasElement();
        return getLastNode().object;
    }

    public boolean contains(E object) {
        Node node = getFirstNodeContainsObject(object);
        return node != null;
    }

    @Override
    public String toString() {
        StringBuilder stringElements = new StringBuilder("[");
        final String coma = ", ";

        if (first != null) {
            Iterator listIterator = iterator();

            while (listIterator.hasNext()) {
                stringElements.append(listIterator.next());

                if (listIterator.hasNext()) stringElements.append(coma);
            }
        }
        stringElements.append("]");

        return stringElements.toString();
    }
}